﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Web;

using umbraco.cms.businesslogic.packager.standardPackageActions;
using umbraco.interfaces;

namespace CreativeMinds.Umbraco.Installation.PackageActions {

	/// <summary>
	/// The package action will merge the content of the source language xml file with the destination UI language xml file.
	/// The structure of the source file must match the structure of the Umbraco UI xml languages files (/umbraco/config/lang/en.xml, /umbraco/config/lang/da.xml etc.)
	/// </summary>
	public class MergeUILanguage : PackageAction, IPackageAction {
		private string targetLanguagePath = "";
		private string sourceLanguagePath = "";
		private XmlDocument target;
		private XmlDocument source;

		/// <summary>
		/// The alias of the package. This alias must be unique and is use to identify the package action to use.
		/// </summary>
		/// <returns>The alias of the package</returns>
		public override string Alias() {
			return "MergeUILanguage";
		}

		/// <summary>
		/// Initializes the different variables, loading the source and destination files into xml documents.
		/// </summary>
		/// <param name="xmlData">The node for the package from the actions xml.</param>
		private void Initialize(XmlNode xmlData) {
			targetLanguagePath = HttpContext.Current.Server.MapPath(GetAttributeValue(xmlData, "target"));
			sourceLanguagePath = HttpContext.Current.Server.MapPath(GetAttributeValue(xmlData, "source"));

			target = new XmlDocument();
			target.Load(targetLanguagePath);

			source = new XmlDocument();
			source.Load(sourceLanguagePath);
		}

		/// <summary>
		/// Install method for the package.
		/// </summary>
		/// <param name="packageName">Name of the package to be installed.</param>
		/// <param name="xmlData">The node for the package from the actions xml.</param>
		/// <returns>True if the installation succeeded</returns>
		protected override bool Install(string packageName, XmlNode xmlData) {
			Initialize(xmlData);

			// Get all area nodes from the source file.
			XmlNodeList areas = source.SelectNodes("language/area");
			foreach (XmlNode area in areas) {
				// The area alias
				XmlAttribute alias = (XmlAttribute)area.Attributes.GetNamedItem("alias");
				if (alias != null) {
					XmlNode targetArea = target.SelectSingleNode(String.Format("language/area[@alias='{0}']", alias.Value));
					// Does the target file already contain an area with the same alias?
					if (targetArea == null) {
						// Target not found, let's create it.
						targetArea = target.CreateElement("area");
						target.SelectSingleNode("language").AppendChild(targetArea);

						// And remember the alias!
						XmlAttribute newAlias = target.CreateAttribute("alias");
						targetArea.Attributes.Append(newAlias);
						newAlias.Value = alias.Value;
					}

					// Get the keys from the source area.
					XmlNodeList keys = area.SelectNodes("key");
					foreach (XmlNode key in keys) {
						// Get the key alias
						XmlAttribute keyAlias = (XmlAttribute)key.Attributes.GetNamedItem("alias");
						if (keyAlias != null) {
							XmlNode targetKey = target.SelectSingleNode(String.Format("language/area[@alias='{0}']/key[@alias='{1}']", alias.Value, keyAlias.Value));
							// Does the target file contain a key with the same alias in the same area?
							if (targetKey == null) {
								// Target key not found, create it!
								targetKey = target.CreateElement("key");
								targetArea.AppendChild(targetKey);

								// And remember the alias
								XmlAttribute newAliasKey = target.CreateAttribute("alias");
								targetKey.Attributes.Append(newAliasKey);
								newAliasKey.Value = keyAlias.Value;
							}
							// Does the target key has children?
							if (targetKey.ChildNodes.Count == 1) {
								// Let's remove them!
								XmlNode child = targetKey.FirstChild;
								targetKey.RemoveChild(child);
							}
							// Insert the text from the source key into the destination key.
							targetKey.AppendChild(target.CreateCDataSection(key.InnerText));
						}
					}
				}
			}
			// Save the destination language file.
			target.Save(targetLanguagePath);

			return true;
		}

		/// <summary>
		/// Returns a sample xml node, in this case the path to the source and target files for the UI texts.
		/// </summary>
		/// <returns>A xml node containing an example of the required data for this package</returns>
		public override XmlNode SampleXml() {
			return helper.parseStringToXmlNode(String.Format("<Action runat=\"install\" alias=\"{0}\" target=\"~/umbraco/config/lang/en.xml\" source=\"~/umbraco/mymodule/install/en.xml\"/>", this.Alias()));
		}

		/// <summary>
		/// Uninstall method for the package.
		/// </summary>
		/// <param name="packageName">Name of the package to be uninstalled.</param>
		/// <param name="xmlData">The node for the package from the actions xml.</param>
		/// <returns>True if the uninstallation succeeded</returns>
		protected override bool Uninstall(string packageName, XmlNode xmlData) {
			Initialize(xmlData);

			Dictionary<XmlNode, List<XmlNode>> deleteList = new Dictionary<XmlNode, List<XmlNode>>();

			// Get all area nodes from the source file.
			XmlNodeList areas = source.SelectNodes("language/area");
			foreach (XmlNode area in areas) {
				// The area alias
				XmlAttribute alias = (XmlAttribute)area.Attributes.GetNamedItem("alias");
				if (alias != null) {
					// Does the target file contain an area with the same alias?
					XmlNode targetArea = target.SelectSingleNode(String.Format("language/area[@alias='{0}']", alias.Value));
					if (targetArea != null) {
						XmlNodeList keys = area.SelectNodes("key");
						foreach (XmlNode key in keys) {
							XmlAttribute keyAlias = (XmlAttribute)key.Attributes.GetNamedItem("alias");
							if (keyAlias != null) {
								XmlNode targetKey = target.SelectSingleNode(String.Format("language/area[@alias='{0}']/key[@alias='{1}']", alias.Value, keyAlias.Value));
								if (targetKey != null) {
									if (!deleteList.ContainsKey(targetKey.ParentNode)) {
										deleteList.Add(targetKey.ParentNode, new List<XmlNode>());
									}
									deleteList[targetKey.ParentNode].Add(targetKey);
								}
							}
						}
					}
				}
			}

			foreach (KeyValuePair<XmlNode, List<XmlNode>> pair in deleteList) {
				foreach (XmlNode key in pair.Value) {
					pair.Key.RemoveChild(key);
				}
				if (pair.Key.ChildNodes.Count == 0) {
					XmlNode parent = pair.Key.ParentNode;
					parent.RemoveChild(pair.Key);
				}
			}

			return true;
		}
	}
}